﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Text.RegularExpressions;

namespace Science.Mathematics.Algebra
{
    public class Fraction
    {
        #region Private Data Members

        private int _numerator = 0;
        private int _denominator = 1;

        #endregion

        #region Properties

        public int Numerator
        {
            get
            {
                return _numerator;
            }
            set
            {
                _numerator = value;
            }
        }

        public int Denominator
        {
            get
            {
                return _denominator;
            }
            set
            {
                if (value == 0)
                    throw new DivideByZeroException("The Denominator cannot be zero");
                _denominator = value;
                if (_denominator < 0)
                    Numerator *= -1;
                _denominator = Math.Abs(_denominator);
            }
        }

        #endregion

        #region Constructors

        public Fraction()
        {
            Numerator = 0;
            Denominator = 1;
        }

        public Fraction(int numerator)
        {
            Numerator = numerator;
            Denominator = 1;
        }

        public Fraction(int numerator, int denomenator)
        {
            Numerator = numerator;
            Denominator = denomenator;
        }

        public Fraction(String fractionStr)
        {
            int index = 0;
            Fraction frc = Fraction.Parse(fractionStr, ref index);
            Numerator = frc.Numerator;
            Denominator = frc.Denominator;
        }

        #endregion

        #region Public Methods
        
        Fraction Simplify()
        {            
			int gcf = MathHelper.GCF(Numerator, Denominator);
			Fraction frc = new Fraction(Numerator / gcf, Denominator / gcf);
			return frc;
        }

        Fraction Reciprocal()
        {
            return new Fraction(Denominator, Numerator);
        }

        #endregion

        #region Static Public Function

        public static Fraction Parse(String str, ref int index)
        {
            if (index >= str.Length)
                throw new InvalidOperationException("Invalid fraction string");

            Fraction fraction = new Fraction();
            Match match = Regex.Match(str, @"\s*(-?\d+)\s*(/)\s*(-?\d+)\s*", RegexOptions.IgnoreCase);
            if (match.Success)
            {
                index = match.Index + match.Length;
                fraction.Numerator = int.Parse(match.Groups[1].Value);
                fraction.Denominator = int.Parse(match.Groups[3].Value);
            }
            else
                throw new InvalidOperationException("Invalid fraction string");
            return fraction;
        }

        public static bool TryParse(String str, ref int index, ref Fraction fraction)
        {
            try
            {
                fraction = Fraction.Parse(str, ref index);
                return true;
            }
            catch
            {
                return false;
            }
        }

        #endregion

        #region Object Virtual Functions

        public override bool Equals(object obj)
        {
            Fraction frc = obj as Fraction;
            return Numerator == frc.Numerator && Denominator == frc.Denominator;
        }

        public override int GetHashCode()
        {
            return Numerator.GetHashCode() + Denominator.GetHashCode();
        }

        public override string ToString()
        {
            return Numerator.ToString() + " / " + Denominator.ToString();
        }

        #endregion

        #region Overloaded Operator

        #region Logical Operators

        public static bool operator ==(Fraction x, Fraction y)
        {
            return x.Numerator == y.Numerator && x.Denominator == y.Denominator;
        }

        public static bool operator !=(Fraction x, Fraction y)
        {
            return !(x == y);
        }

        public static bool operator <(Fraction x, Fraction y)
        {
            return (double)x < (double)y;
        }

        public static bool operator >(Fraction x, Fraction y)
        {
            return (double)x > (double)y;
        }

        public static bool operator <=(Fraction x, Fraction y)
        {
            return (double)x <= (double)y;
        }

        public static bool operator >=(Fraction x, Fraction y)
        {
            return (double)x >= (double)y;
        }

        #endregion

        #region Mathematics Operators

        public static Fraction operator+(Fraction x, Fraction y)
        {
            if (x.Denominator == y.Denominator)
                return (new Fraction(x.Numerator + y.Numerator, x.Denominator)).Simplify();
            else
                return (new Fraction((x.Numerator * y.Denominator) + (y.Numerator * x.Denominator), 
                    x.Denominator * y.Denominator)).Simplify();
        }

        public static Fraction operator -(Fraction x, Fraction y)
        {
            if (x.Denominator == y.Denominator)
                return (new Fraction(x.Numerator - y.Numerator, x.Denominator)).Simplify();
            else
                return (new Fraction((x.Numerator * y.Denominator) - (y.Numerator * x.Denominator),
                    x.Denominator * y.Denominator)).Simplify();
        }

        public static Fraction operator *(Fraction x, Fraction y)
        {      
            return (new Fraction(x.Numerator * y.Numerator, x.Denominator * y.Denominator)).Simplify();
        }

        public static Fraction operator /(Fraction x, Fraction y)
        {
            if ((double)y == 0)
                throw new DivideByZeroException();

            return (new Fraction(x.Numerator * y.Denominator, x.Denominator * y.Numerator)).Simplify();
        }

        #endregion

        #region Conversion Operators

        public static implicit operator Fraction(double x)
        {            
            int factor = 1;
            while ((x * factor) - (int)(x * factor) != 0)
                factor *= 10;

            Fraction frc = new Fraction((int)(x * factor), factor);
            frc = frc.Simplify();
            return frc;
        }

        public static explicit operator double(Fraction x)
        {
            return (double)(x.Numerator) / (double)(x.Denominator);
        }

        public static implicit operator Fraction(String str)
        {
            int index = 0;
            return Fraction.Parse(str, ref index);
        }

        public static explicit operator String(Fraction x)
        {
            return x.ToString();
        }

        #endregion

        #endregion
    }
}
